#include <ximera/system/application.hpp>
#include <ximera/logger.hpp>

#include <iostream>

using namespace ximera;

struct GraphicsTestHandler : system::AppHandler
{
    GraphicsTestHandler(system::Application& app)
        : application(app) {}

	virtual void onStart(system::AppEventArgs& e)
    {
        try
        {
            e.app.inputSystem()->keyboard()->onKeyDown() += 
                boost::bind(&GraphicsTestHandler::keyboard_KeyDown, this, _1, _2);

            setupWorld();
            setupCamera();
            setupLights();
            setupEntities();
            setupParticles();
        }
        catch (ximera::Exception& e)
        {
            application.shouldClose(true);
            XIMERA_LOG() << e << std::endl;
        }
    }

	virtual void onUpdate(system::UpdateEventArgs& e)
    {
        entity->step(e.secs);

        math::Vector3 moveDir;

        if (application.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::W))
            moveDir = camera->direction();
        else if (application.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::S))
            moveDir = -camera->direction();

        if (application.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::A))
            moveDir += -camera->right();
        else if (application.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::D))
            moveDir += camera->right();

        if (moveDir != math::Vector3::ZERO)
        {
            Real const velocity = 20.0f;
            camera->position(camera->position() + (moveDir * (velocity * e.secs)));
        }

        Real rotation = -application.inputSystem()->mouse()->axisRel(1);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 40.05f;
            rotation = rotation * velocity;

            camera->pitch(rotation);
        }

        rotation = -application.inputSystem()->mouse()->axisRel(0);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 35.0f;
            rotation = rotation * velocity;

            camera->yaw(rotation);
        }
    }

private:
    SharedPtr<graphics::IWorld> graphicsWorld;
    SharedPtr<graphics::ICamera> camera;
    SharedPtr<graphics::IViewport> viewport;
    SharedPtr<graphics::ILight> light;
    SharedPtr<graphics::ILight> light2;
    SharedPtr<graphics::IEntity> entity;
    SharedPtr<graphics::IParticleSystem> particleSystem;

    system::Application& application;

    void setupWorld()
    {
        graphicsWorld.reset(application.graphicsSystem()->createWorld());
        
        application.graphicsSystem()->ambientLight(math::Color(0.5f, 0.5f, 0.5f));
        application.graphicsSystem()->skyDome(L"Examples/CloudySky");        
    }

    void setupCamera()
    {
        camera.reset(graphicsWorld->createCamera(L"default"));
        camera->position(math::Vector3(20, 5, 20));
        camera->lookAt(math::Vector3(0, 0, -1));
        //camera->renderMode(graphics::RenderMode::Points);

        viewport.reset(graphicsWorld->createViewport(camera.get()));
        //viewport->backgroundColor(math::Color::WHITE);
        //viewport->height(1.0f);

        graphics::ISceneNode* cameraNode = graphicsWorld->rootNode()->createChild(L"camera");
        cameraNode->attach(camera.get());
    }

    void setupLights()
    {
        light.reset(graphicsWorld->createLight(L"light"));
        light->position(math::Vector3(20,80,50));
        light->diffuseColor(math::Color(10, 110, 0, 255));
        light->type(graphics::LightType::Directional);

        light2.reset(graphicsWorld->createLight(L"light2"));
        light2->position(math::Vector3(20,80,50));
        light2->enabled(false);
        light2->type(graphics::LightType::Point);
    }

    void setupEntities()
    {
        //entity.reset(graphicsWorld->createEntity(L"cube", graphics::PrefabEntityType::Cube));
        //entity->materialName(L"RustyBarrel");
        //entity.reset(graphicsWorld->createEntity(L"cube", L"Barrel.mesh"));
        entity.reset(graphicsWorld->createEntity(L"cube", L"robot.mesh"));
        //graphicsWorld->rootNode()->createChild(L"cube")->attach(entity.get());
        //entity.reset(graphicsWorld->createEntity(L"cube", L"jaiqua.mesh"));
        //entity->enableAnimation(L"Sneak");

        graphics::ISceneNode* entityNode = graphicsWorld->rootNode()->createChild(L"cube");
        entityNode->attach(entity.get());
        entityNode->position(math::Vector3(0, 0, 0));
        entityNode->scale(.5f);
    }

    void setupParticles()
    {
        //particleSystem.reset(graphicsWorld->createParticleSystem(L"smoke", L"Examples/Rain"));
        particleSystem.reset(graphicsWorld->createParticleSystem(L"smoke", L"Examples/Smoke"));
        particleSystem->speedFactor(0.3f);
        //particleSystem->setDefaultDimensions(1, 1);

        graphics::ISceneNode* particleNode = graphicsWorld->rootNode()->createChild(L"particle");
        particleNode->attach(particleSystem.get());            
        particleNode->position(math::Vector3(15, -40, 0));
    }

    void keyboard_KeyDown(input::IKeyboard* sender, input::KeyCode const& keyCode)
    {
        switch (keyCode.value)
        {
        case input::KeyCode::Escape:
            application.shouldClose(true);
            break;

        case input::KeyCode::L:
            light->enabled(!light->enabled());
            light2->enabled(!light2->enabled());
            break;
        }
    }
};

int main()
{
    try
    {
        XIMERA_LOG().stream(&std::wcout);

        system::AppSettings settings;
        settings[L"ximera.plugins.input"] = XIMERA_PLUGIN(L"XimeraDInput8");
        settings[L"ximera.plugins.graphics"] = XIMERA_PLUGIN(L"XimeraOgre");
        settings[L"ximera.ogre.showdialog"] = L"1";

        system::Application app(settings);

        GraphicsTestHandler handler(app);
        app.addHandler(handler);

        return app.run();
    }
    catch (ximera::Exception& e)
    {
        XIMERA_LOG() << e << std::endl;
    }
}
